<?
    /**
     * @name     VK4DS
     * @version  3.0.0 Beta
     * @author   DENFER (iC-Team)
     * @license  http://creativecommons.org/licenses/by-nc-sa/3.0/
     * @charset  WINDOWS-1251
     */

    Abstract Class __a
    {
        Static Function get($self){
            return TThread::get($self);
        }
        Static Function setCallBack($self,$res){
            $self = __a::get($self);
            $GLOBALS['__aCallBack'][$self->____name] = $res;
        }
        Static Function callAfter($self,$ms,$arg = null){       
            Timer::setInterval(Function($timer) use ($self,$arg){
                $self = __a::get($self);
                if($arg)
                    $self->____arg = $arg;
                if($GLOBALS['__aCallBack'][$self->____name])
                    __a::Call($self->____name,false,$self->____arg);            
                Timer::clearTimer($timer);
            },$ms);
        }
        Static Function resultAfter($self,$ms,$res){ 
            Timer::setInterval(Function($timer) use ($self,$res){
                $self = __a::get($self);
                $v = $GLOBALS['__aCallBack'][$self->____name];  
                if(!$v) return;
                $v($self,$res);
                Timer::clearTimer($timer);
            },$ms);
        }
        Static Function result($self,$res){ 
            $self = __a::get($self);
            $v = $GLOBALS['__aCallBack'][$self->____name];  
            if(!$v) return;
            $v($self,$res);
        }
        Static Function callEx($self,$arg = null){
            $self = __a::get($self);
            if($arg)
                $self->____arg = $arg;
            __a::Call($self->____name,false,$self->____arg);
        }
        Static Function Clear($self){
            $self = __a::get($self);
            unset($GLOBALS['__aCallBack'][$self->____name]);
        }
        Static Function syncResult($self,$name,$res){
            $v = $GLOBALS['__aCallBack'][$name];
            if($v)
                $v($self,$res);
        }
        Static Function threadCall($t){
            if(!$GLOBALS['THREAD_SELF']) return;
            $self = TThread::get($t);
            $res = call_user_func_array($self->____name, $self->____arg);
            sync('__a::syncResult',array($t,$self->____name,$res));
        }
        Static Function c($name,$callBack, $args = null){
            if($GLOBALS['THREAD_SELF']) return;
            if($callBack)
                $GLOBALS['__aCallBack'][$name] = $callBack; 
            $name = (string)$name;
            list($object, $method_name) = explode('::', $name);
            if((!Function_exists($name))&&(!method_exists($object, $method_name)))
                return false;
            $t = new TThread('__a::threadCall');        
            $t->____name = $name;
            $t->____arg = $args; 
            $t->resume(); 
            return true;
        }
    }

    Class Prototype
    {
        Protected $Override = true;
        
        Public Function __get($attribute)
        {
            $Func = Array($this,'get_'.$attribute);
            IF(is_callable($Func))
                return @call_user_func($Func);
            return null;
        }
        
        Public Function __set($attribute, $value)
        {
            $Func = Array($this,'set_'.$attribute);
            IF(is_callable($Func))
                return @call_user_func($Func, $value);
            IF($this->Override)
                return $this -> {$attribute} = $value;
            return null;
        }
        
        Public Function __setAll($value)
        {
            ForEach( (Object) $this As $Attribute => $__Value )
                IF( $Attribute != 'Override' )
                    $this -> {$Attribute} = $value;
        }
        
        Public Function Assign($Object)
        {
            ForEach( (Object) $this As $Attribute => $__Value )
                IF( $Attribute != 'Override' )
                    IF(is_array($Object))
                        IF(($Object[$Attribute]))
                            self::__set($Attribute, $Object [$Attribute]);
                    ELSEIF(is_object($Object))
                        IF(($Object->{$Attribute}))
                            self::__set($Attribute, $Object -> {$Attribute});
             return $this;
        }
        
        Public Function Dump()
        {
            $Dump = new self;
            return $Dump->Assign($this);
        }
        
        Public Function ToString()
        {
            $String = '';
            ForEach( (Object) $this As $Attribute => $Value )
                $String .= $Attribute.' = '.$Value."\r\n";
            return substr($String,0,strlen($String)-2);
        }
    }
    
    Class ReadOnly Extends Prototype
    {
        Public Function __set($attribute, $value){ IF(is_array($value)) IF(sizeof($value)>1) IF($value[0] === WRITABLE) return parent::__set($attribute,$value[1]); IF(isset($this->{$attribute})) return; IF($this->{$attribute} == null) parent::__set($attribute,$value); }
    }
    
    Class Event
    {
        Protected $Handle = Array();
        
        Public Function AddHandle($Function)
        {
            IF(is_callable($Function))
            {
                $this->Handle[] = $Function;
                return sizeof($this->Handle)-1;
            }
            return null;
        }
        
        Public Function RemoveHandle($Id)
        {
            IF(isset($this->Handle[$Id]))
                unset($this->Handle[$Id]);
        }
        
        Public Function Emit(){@call_user_func_array(array($this,'Call'), func_get_args());}
        Public Function Call()
        {
            IF(sizeof($this->Handle)>0)
                ForEach( (Array) $this->Handle As $Function )
                    IF(is_callable($Function))
                        @call_user_func_array($Function, func_get_args());
        }
    }
    
    DEFINE ( 'WRITABLE', 0xFF * 0xFA );
    
    DEFINE ( 'undefined', 0xFF * 0xFC );
    DEFINE ( 'undef', undefined );
    
    DEFINE ( 'DEFAULT_SESSION', 1 );
    DEFINE ( 'DEFAULT_APPID', 1968179 );
    
    Class dtime Extends Prototype
    {
        Protected $Time;
        
        Public Function __construct($Time = null)
        {
            IF(!$Time) $Time = time();
            $this->Time = $Time;
        }
        
        Public Static Function __ToMinute($value)
        {
            return $value * 60;
        }
        
        Public Static Function __ToHour($value)
        {
            return self::__ToMinute($value) * 60;
        }
        
        Public Static Function __ToDay($value)
        {
            return self::__ToHour($value) * 24;
        }
        
        Public Static Function __ToWeek($value)
        {
            return self::__ToDay($value) * 7;
        }
        
        Public Static Function __ToMonth($value)
        {
            return self::__ToDay($value) * 30;
        }
        
        Public Function get_TotalSeconds()
        {
            return time() - $this->Time;
        }
        
        Public Function get_TotalMinutes()
        {
            return self::__ToMinute(time()) - self::__ToMinute($this->Time);
        }
        
        Public Function get_TotalHours()
        {
            return self::__ToHour(time()) - self::__ToHour($this->Time);
        }
        
        Public Function get_TotalDays()
        {
            return self::__ToDay(time()) - self::__ToDay($this->Time);
        }
        
        Public Function get_TotalWeeks()
        {
            return self::__ToWeek(time()) - self::__ToWeek($this->Time);
        }
        
        Public Function get_TotalMonths()
        {
            return self::__ToMonth(time()) - self::__ToMonth($this->Time);
        }
        
        Public Function get_Seconds()
        {
            return date('s',$this->Time);
        }
        
        Public Function get_Minutes()
        {
            return date('i',$this->Time);
        }
        
        Public Function get_Hours()
        {
            return date('H',$this->Time);
        }
        
        Public Function get_Day()
        {
            return date('d',$this->Time);
        }
        
        Public Function get_Month()
        {
            return date('m',$this->Time);
        }
        
        Public Function get_Year()
        {
            return date('Y',$this->Time);
        }
    }
    
    Class Friend Extends ReadOnly
    {
        Protected $API = null;
        
        Public $IsUpdated = false;
        Public $LastUpdate;
        
        Public $uid;
        Public $first_name;
        Public $last_name;
        Public $nickname;
        Public $screen_name;
        Public $sex;
        Public $bdate;
        Public $city;
        Public $country;
        Public $timezone;
        Public $photo;
        Public $photo_medium;
        Public $photo_big;
        Public $has_mobile;
        Public $rate;
        Public $contacts;
        Public $education;
        Public $online;
        Public $counters;
        Public $last_seen;
        Public $wall_comments;
        Public $relation;
        Public $activity;
        Public $can_write_private_message;
        Public $can_see_all_posts;
        Public $can_post;
        
        Public $OnUpdate;        
        
        Public $Fields = array('uid','first_name','last_name','nickname','screen_name',
                'sex','bdate','city','country','timezone',
                'photo','photo_medium','photo_big','has_mobile',
                'rate','contacts','education','online','counters',
                'last_seen','wall_comments','relation','activity',
                'can_write_private_message','can_see_all_posts',
                'can_post');
        
        Public $OldState;
        Public $SaveOldState = true;
                
        Public Function __construct($id = null, VK4DS $API = null)
        {
            $this->OnUpdate = new Event;
            
            IF(!$API)
                $this->API = VK4DS::last();
            IF(!$this->API)
                throw new Exception('VK4DS Never declared before.');
            
            IF($id)
            {
                IF(is_string($id))
                    $this->screen_name = $id;
                ELSEIF(is_numeric($id))
                    $this->uid = $id;
                ELSE return;
                
                $this->Update();
            }
        }
        
        Public Function SendMessage($Message, $Title = undefined, $ResponseCallBack = null)
        {
            $this->API->c('messages.send', Array('uid'=>$this->uid, 'domain'=>$this->screen_name, 'message'=>$Message,'title'=>$Title),$ResponseCallBack);
        }
        
        Public Function get_id()
        {
            IF($this->uid)
                return $this->uid;
            ELSEIF($this->screen_name)
                return $this->screen_name;
            return null;
        }
        
        Public Function Update()
        {
            IF(!$this->API)
            {
                throw new Exception('VK4DS object is not valid');
                return null;
            }
            
            $id = $this->id;
            
            IF(!$id){ $this->OnError->Call('Invalid friend id.'); return null; }
            
            $self = $this;
            
            IF($this->SaveOldState)
            {
                $this->OldState = new Friend;
                $this->OldState->Assign($self);
            }
            
            $this->API->c('users.get', array( 'uids' => $id, 'fields' => implode(',',$this->Fields)), 
            function($response)use($self)
            {
                IF(!$response['response']) return;
                $self->IsUpdated = true;
                $self->LastUpdate = new dtime;
                $Counters = new Counters;
                $self->Assign($response['response'][0]);
                $Counters->Assign($response['response'][0]['counters']);
                $self->counters = $Counters;
                $self->last_seen = $self->last_seen['time'];
                $self->OnUpdate->Call($self);
            });
        }
    }
    
    Class Counters Extends ReadOnly
    {
        Public $albums;
        Public $videos;
        Public $audios;
        Public $notes;
        Public $friends;
        Public $online_friends;
        Public $user_photos;
        Public $user_videos;
        Public $followers;
        Public $subscriptions;
    }
    
    Class Scope Extends Prototype
    {
        Protected $Override = false;
        
    	Public $Notify = false;
        Public $Friends = false;
        Public $Photos = false;
        Public $Audio = false;
        Public $Video = false;
        Public $Docs = false;
        Public $Notes = false;
        Public $Pages = false;
        Public $Offers = false;
        Public $Questions = false;
        Public $Wall = false;
        Public $Groups = false;
        Public $Messages = false;
        Public $Notifications = false;
        Public $Stats = false;
        Public $Ads = false;
        Public $Offline = true;
        Public $Nohttps = false;
        
        Public Function __construct($Fullaccess = false)
        {
            IF($Fullaccess)
                $this->FullAccess(true);                
        }
        
        Public Function FullAccess($state = true)
        {
            $this->__setAll($state);
            $this->Nohttps = false;
        }
        
        Public Function __ToString()
        {
            $String = '';
            ForEach( (Object) $this As $Attribute => $Value )
                IF($Value)
                    $String .= strtolower($Attribute).',';
            return substr($String,0,strlen($String)-1);
        }
    }
    
    Class AuthenticateData Extends ReadOnly
    {
        Public $access_token = null;
        Public $expires_in;
        Public $user_id;
        Public $secret;
        
        Public $Data;
                
        Public Function __construct($AuthenticateData)
        {
            $this->Data = $AuthenticateData;
            ForEach( (Array)Explode( '&', $AuthenticateData ) As $Items  )
            {
                $Item = Explode( '=', $Items );
                IF(sizeof($Item)>0)
                    IF(strlen($Item[0])>0)
                    {
                        $Key = $Item[0];
                        $Value = null;
                        IF(sizeof($Item)>1)
                            $Value = $Item[1];
                        $this->{$Key} = $Value;
                    }
            }
        }
    }
    
    Function VK4DS_Decode($input){return iconv('utf-8', 'cp1251', $input);}
    Function VK4DS_Encode($input){return iconv('cp1251', 'utf-8', $input);}
    
    
    Function UTF8_CP1251(&$value, $key){$value = VK4DS_Decode($value);}
    Function CP1251_UTF8(&$value, $key){$value = VK4DS_Encode($value);}
    
    Abstract Class CaptchaBox Extends ReadOnly
    {
        Public Static $Form;
        Public Static $Captcha;
        Public Static $Input;
        Public Static $OK;
        Public Static $Cancel;
        Public Static $Result = null;
        
        Public Static Function __init()
        {
            $AcceptHandle = function()
            {
                CaptchaBox::$Result = CaptchaBox::$Input->Text;
                CaptchaBox::$Input->Text = '';
                CaptchaBox::$Form->Close();
            };
            
            self::$Form = new TForm;
            self::$Form->Caption = 'VK4DS3 CaptchaBox';
            self::$Form->borderStyle = bsDialog;
            self::$Form->w = 150;
            self::$Form->h = 160;
            self::$Form->position = poOwnerFormCenter;
            
            $Border = new TShape(self::$Form);
            $Border->parent = self::$Form;
            $Border->w = 134;
            $Border->h = 54;
            $Border->x = (self::$Form->w/2-$Border->w/2)-2;
            $Border->y = 5;
            
            self::$Captcha = new TCaptcha(self::$Form);
            self::$Captcha->parent = self::$Form;
            self::$Captcha->w = 130;
            self::$Captcha->h = 50;
            self::$Captcha->x = $Border->x+2;
            self::$Captcha->y = $Border->y+2;
            self::$Captcha->OnClick = function()
            {
                CaptchaBox::$Captcha->loadFromUrl(CaptchaBox::$Captcha->Captcha);
            };
            
            self::$Input = new TEdit(self::$Form);
            self::$Input->parent = self::$Form;
            self::$Input->w = $Border->w;
            self::$Input->x = $Border->x;
            self::$Input->y = (self::$Form->h/2-self::$Input->h/2) - 3;
            self::$Input->maxLength = 5;
            self::$Input->font->size = 15;
            self::$Input->onKeyDown = function($self,&$key,$shift)use($AcceptHandle)
            {
                IF($key == VK_RETURN) $AcceptHandle();   
            };
            
            self::$OK = new TButton(self::$Form);
            self::$OK->parent = self::$Form;
            self::$OK->w = $Border->w;
            self::$OK->x = $Border->x;
            self::$OK->y = self::$Input->y + self::$OK->h + 15;
            self::$OK->Caption = 'OK';
            self::$OK->OnClick = $AcceptHandle;
        }
        
        Public Static Function Show($Captcha)
        {
            $Captcha = $Captcha.'?/.jpg';
            self::$Result = null;
            self::$Input->Text = '';
            self::$Captcha->Captcha = $Captcha;
            self::$Captcha->loadFromUrl($Captcha);
            self::$Form->showModal();
            return trim(self::$Result);
        }
    }
    
    Class TCaptcha Extends TMImage
    {
        Public $Captcha;
    }
    
    CaptchaBox::__init();
    
    Class VK4DS Extends ReadOnly
    {
        Public $SSL = false;
        Public $Session = '';
        
        Protected $AuthenticateData;
        Protected $Library;
        Protected $SessionFile = '';
        
        Public $OnError;
        
        Public Function __construct($SessionId = DEFAULT_SESSION)
        {
            $this->OnError = new Event;
            
            IF($SessionId !== null)
            {
                $this->Session = strtoupper( md5( json_encode($SessionId) ) );
                $this->SessionFile = sys_get_temp_dir().'\\VK4DS3_'. $this->Session . '.tmp';

                IF(file_exists($this->SessionFile))
                    $this->LoadSession();
            }
            
            IF(file_exists('VK4DS3.dll'))
                $this->Library = new DOTNET('VK4DS3', 'VK4DS3.Authentication');
            else
                $this->OnError->Call('Library "VK4DS3.dll" not found.');
            
            $GLOBALS['VK4DS'][] = $this;
        }
                
        Public Static Function __BuildQuery($Array)
        {
            IF( !(sizeof($Array) > 0) ) return '';
            $Query = '';
            ForEach( (Array) $Array As $attribute => $value )
            {
                IF($value !== undefined)
                    $Query .= $attribute . '=' . urlencode( VK4DS_Encode($value) ) . '&';
            }
            return $Query;
        }
        
        Public Static Function __Send( $Request, $Signature = null, $SSL = false )
        {
            $Domain = 'api.vk.com';
            $RequestURL = $Domain.$Request;
            
            IF($SSL)
            {
            	$Protocol = 'https';
                $RequestURL = $Protocol.'://'.$RequestURL;
                $Curl = curl_init( $RequestURL );
                curl_setopt ( $Curl, CURLOPT_USERAGENT, "VK4DS3 HTTP Client" );
                curl_setopt ( $Curl, CURLOPT_SSL_VERIFYPEER, FALSE );
                curl_setopt ( $Curl, CURLOPT_RETURNTRANSFER, 1 );
                $Response = curl_exec ( $Curl );
                curl_close ( $Curl );
                return $Response;
            }
            ELSEIF($Signature)
            {
            	$Protocol = 'http';
                $RequestURL = $Protocol.'://'.$RequestURL.'&sig='.$Signature;
                return file_get_contents($RequestURL);
            }
            
            throw new Exception('Cant send web request: invalid signature');
            return null;
        }
        
        Public Function Call($Method, $Arguments = null, $CallBack = null){return $this->c($Method, $Arguments, $CallBack);}
        Public Function c($Method, $Arguments = null, $CallBack = null)
        {
            IF(!$Arguments) $Arguments = array();
            $Request = '/method/'.$Method.'?'.$this->__BuildQuery($Arguments).'access_token='.$this->AuthenticateData->access_token;
            
            $Signature = null;
            
            IF(!$this->SSL)
                $Signature = md5($Request.$this->AuthenticateData->secret);
            
            $API = $this;
            
            __a::c('VK4DS::__Send', function($self, $response) use ($API, $Method, $Arguments, $CallBack)
            {
                IF($response)
                {
                    $Object = json_decode( $response, true );
                    array_walk_recursive( $Object, 'UTF8_CP1251' );
                    IF(isset($Object['error']))
                    {
                        IF(isset($Object['error']['error_code']))
                            switch($Object['error']['error_code']+0)
                            {
                                case 14:
                                {
                                    $captcha_sid = $Object['error']['captcha_sid'];
                                    $captcha_img = $Object['error']['captcha_img'];
                                    $captcha_key = CaptchaBox::Show($captcha_img);
                                    $Arguments['captcha_sid'] = $captcha_sid;
                                    $Arguments['captcha_key'] = $captcha_key;
                                    $API->c($Method,$Arguments,$CallBack);
                                } break;

                                default:
                                    IF(isset($Object['error']['error_msg']))
                                        $API->OnError->Call($Object['error']['error_msg']);
                            }
                    }
                    ELSEIF(is_callable($CallBack))
                        call_user_func($CallBack, $Object['response']);
                }
                $thread = __a::get($self);
                $thread->terminate();
                $thread->free();
            }, Array($Request,$Signature,$this->SSL) );
        }
        
        Public Function get_IsAuthed()
        {
            return $this->AuthenticateData->access_token !== null;
        }
        
        Public Function ClearSession()
        {
            $Sessions = glob( sys_get_temp_dir().'\\VK4DS3_*.tmp' );
            ForEach( (Array) $Sessions As $SessionFile )
                @unlink($SessionFile);
        }
        
        Public Function SaveSession()
        {
            IF($this->SessionFile)
                return @file_put_contents($this->SessionFile, $this->AuthenticateData->Data);
            return false;
        }
        
        Public Function LoadSession()
        {
            IF($this->SessionFile)
                return $this->AuthenticateFromFile($this->SessionFile);
            return false;
        }
        
        Public Function AuthenticateFromFile($File)
        {
            IF(!file_exists($File)) $this->OnError->Call('File "'.$File.'" not found.');
            $AuthenticateData = new AuthenticateData(@file_get_contents($File));
            IF($AuthenticateData->access_token)
            {
                $this->AuthenticateData = $AuthenticateData;
                return true;
            }
            return false;
        }
        
        
        Public Function Authenticate(Scope $Scope = null, $AppId = DEFAULT_APPID)
        {
            IF(!$Scope)
                $Scope = new Scope(true);
            IF(!$this->SSL)
                $Scope->Nohttps = true;
            IF($this->Library->Authenticate($AppId, $Scope->__ToString()))
            {
                $AuthenticateData = new AuthenticateData($this->Library->AuthenticateData);
                IF($AuthenticateData->access_token)
                {
                    $this->AuthenticateData = $AuthenticateData;
                    $this->SaveSession();
                    return true;
                }
            }
            
            return false;
        }
        
        Public Static Function instance($SessionId = DEFAULT_SESSION)
        {
            return new self($SessionId);
        }
        
        Public Static Function last()
        {
            IF(sizeof($GLOBALS['VK4DS']) > 0)
                return $GLOBALS['VK4DS'][ sizeof($GLOBALS['VK4DS'])-1 ];
            return null;
        }
    }